use crate::{
    chat::{ChatRequest, ChatResponse, ChatMessage, ChatRequestBuilder, ToolDefinition}, 
    image::{ImageRequest, ImageResponse}
};

use std::time::Duration;
use reqwest::Client;
use async_trait::async_trait;
use anyhow::{Context, Result};
use serde::{de::DeserializeOwned, Serialize};

const TIMEOUT: u64 = 60;

#[async_trait]
pub trait ApiRequest: Serialize {
    type Response: DeserializeOwned;
    async fn send(&self, url: &str, api_key: &str) -> Result<Self::Response> {
        // Build response
        Client::new()
            // Add url
            .post(url)
            // Serde request to json string as POST value     
            .json(&self)        
            // Add Authorization
            .bearer_auth(api_key)
            // Set timeout
            .timeout(Duration::from_secs(TIMEOUT))
            // Send http request
            .send()
            .await.context("Send http request")?
            // Convert response content(json format) to `ChatResponse` 
            .json::<Self::Response>()
            .await.context("Deserialize to json")
    }
}

pub struct LlmSdk;

impl LlmSdk {
    pub async fn chat(&self, base_url: &str, api_key: &str, request: &ChatRequest) -> Result<ChatResponse> {
        let url = format!("{}/chat/completions", base_url);
        request.send(&url, api_key).await
    }

    pub async fn chat_builder(&self, base_url: &str, api_key: &str, builder: &ChatRequestBuilder) -> Result<ChatResponse>  {
        let request = builder.build().context("Build request")?;
        self.chat(base_url, api_key, &request).await
    }
    
    pub async fn chat_with_tools(
        &self, 
        base_url: &str, 
        api_key: &str, 
        messages: impl Into<Vec<ChatMessage>>,
        tools: impl Into<Vec<ToolDefinition>>,
    ) -> Result<ChatResponse> 
    {
        let request = ChatRequestBuilder::default()
            .messages(messages)
            .tools(tools)
            .build().context("Build request")?;
        self.chat(base_url, api_key, &request).await
    }
    
    pub async fn image(&self, base_url: &str, api_key: &str, request: &ImageRequest) -> Result<ImageResponse> {
        let url = format!("{}/images/generations", base_url);
        request.send(&url, api_key).await
    }

    pub fn new() -> Self {
        Self
    } 
}